// Note: Battles are *not* database-backed.

const discord = require('discord.js')
const _ = require('lodash')
const time = require('../util/time')
const Party = require('../character/party')

class Battle {
  constructor(game) {
    this.game = game
    this.ticks = 0
    this.teams = []
  }

  // Adds a team (Party) to the fight.
  async addTeam(party) {
    const everyoneRole = this.game.guild.id
    const channel = await this.game.guild.createChannel('battle', 'text', [
      // Permissions! Beware; here be demons.
      {id: everyoneRole, deny: 3136, allow: 0}, // -rw -react
      ...party.members.filter(char => char.discordID).map(char => {
        return {id: char.discordID, deny: 0, allow: 3072} // +rw
      })
    ])

    for (const char of party.members) {
      char._battle = {
        ai: new char.BattleAI(char, this, {channel, party}),
        next: 'choice', // or 'action', 'wait'
        ticksUntil: 0,
        moveChosen: undefined,
        targetsChosen: undefined,
      }
    }

    this.teams.push({channel, party})
  }

  // Advances the battle by one in-game second.
  async tick() {
    if (await this.isComplete()) {
      // ??
      const e = new Error('Battle complete but tick() called')
      e.battle = this
      throw e
    }

    // TODO: progress bar with character avatars instead of this
    /*
    await Promise.all(this.teams.map(({ party, channel }) => {
      return channel.send(party.members.map(char => {
        return `**${char.getName(this.game.guild)}**: ${char._battle.ticksUntil}s until ${char._battle.next}!`
      }).join('\n'))
    }))
    */

    for (const char of _.shuffle(this.everyone)) {
      const characterLabel = await char.getLabel(this.game)

      if (char.healthState === 'dead') {
        // They're dead, so they cannot act.
      } else if (char._battle.ticksUntil > 0) {
        // Nothing to do.
        char._battle.ticksUntil--

        // TODO: use speed stat when next === 'choice'
      } else if (char._battle.next === 'choice') {
        // Decision time!
        const { move, targets } = await char._battle.ai.moveChoice(char, this)

        await this.sendMessageToAll(`${characterLabel} is preparing to use _${move.name}_...`)

        Object.assign(char._battle, {
          next: 'action',
          ticksUntil: move.basePrepareTicks, // TODO: use move 'mastery'
          moveChosen: move,
          targetsChosen: targets,
        })
      } else if (char._battle.next === 'action') {
        // Perform the move.
        const { moveChosen, targetsChosen } = char._battle

        await this.sendMessageToAll(`${characterLabel} used _${moveChosen.name}_!`)

        for (const target of targetsChosen) {
          await moveChosen.performOn(target, char, this)
        }

        Object.assign(char._battle, {
          next: 'wait',
          ticksUntil: moveChosen.baseCooldownTicks, // TODO: use move 'mastery'
          moveChosen: undefined,
          targetsChosen: undefined,
        })
      } else if (char._battle.next === 'wait') {
        // Cooldown complete.
        Object.assign(char._battle, {
          next: 'choice',
          ticksUntil: 5, // TODO use gear weight/speed stat to calculate this
        })
      }
    }

    await time.sleep(time.SECOND)
    this.ticks++

    if (await this.isComplete()) {
      await this.sendMessageToAll('Battle complete')
      await time.sleep(time.SECOND * 10)

      await this.cleanUp()

      return null
    } else {
      return this
    }
  }

  // Every character of every team, in a one-dimensional array.
  get everyone() {
    return _.flatten(this.teams.map(({ party }) => party.members))
  }

  // Helper function for sending a message to all team channels.
  sendMessageToAll(msg) {
    return Promise.all(this.teams.map(team =>
      team.channel.send(msg)
    ))
  }

  // Returns the battle channel for the passed character's team.
  channelOf(char) {
    for (const { party, channel } of this.teams) {
      if (party.members.find(mem => {
        return mem.discordID === char.discordID
      })) {
        return channel
      }
    }

    return null
  }

  // Called once the battle is complete.
  async cleanUp() {
    // Delete battle channels
    await Promise.all(this.teams.map(team => team.channel.delete()))
  }

  // A battle is "complete" if every team but one has 0 HP left.
  async isComplete() {
    return this.teams.filter(({ party }) => {
      for (const char of party.members) {
        if (char.health > 0) return true // Alive member
      }

      return false // Entire team is dead :(
    }).length <= 1
  }
}

module.exports = Battle
